Introduction¶
For this assignment, I will complement Dr. Snow's cholera map by adding new variables and giving more context to the people involved in the tragedy. The purpose is to provide a humane background to the fatalities and bring the user closer to the story.
I will use fictional and real data to differentiate each individual's characteristics and create relationships between them to support the root cause of the problem and eliminate false hypotheses.
Although Dr. Snow's original effort of conveying information was revolutionary, a few extra details will make this analysis easier for the general public.
The fictional data that I will use is: Gender, Age, Name, Profession and Weather category
pip install geopandas
Collecting geopandas Downloading geopandas-1.1.1-py3-none-any.whl.metadata (2.3 kB) Requirement already satisfied: numpy>=1.24 in /opt/anaconda3/lib/python3.13/site-packages (from geopandas) (2.1.3) Collecting pyogrio>=0.7.2 (from geopandas) Downloading pyogrio-0.11.1-cp313-cp313-macosx_12_0_arm64.whl.metadata (5.3 kB) Requirement already satisfied: packaging in /opt/anaconda3/lib/python3.13/site-packages (from geopandas) (24.2) Requirement already satisfied: pandas>=2.0.0 in /opt/anaconda3/lib/python3.13/site-packages (from geopandas) (2.2.3) Collecting pyproj>=3.5.0 (from geopandas) Downloading pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl.metadata (31 kB) Requirement already satisfied: shapely>=2.0.0 in /opt/anaconda3/lib/python3.13/site-packages (from geopandas) (2.1.2) Requirement already satisfied: python-dateutil>=2.8.2 in /opt/anaconda3/lib/python3.13/site-packages (from pandas>=2.0.0->geopandas) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in /opt/anaconda3/lib/python3.13/site-packages (from pandas>=2.0.0->geopandas) (2024.1) Requirement already satisfied: tzdata>=2022.7 in /opt/anaconda3/lib/python3.13/site-packages (from pandas>=2.0.0->geopandas) (2025.2) Requirement already satisfied: certifi in /opt/anaconda3/lib/python3.13/site-packages (from pyogrio>=0.7.2->geopandas) (2025.8.3) Requirement already satisfied: six>=1.5 in /opt/anaconda3/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=2.0.0->geopandas) (1.17.0) Downloading geopandas-1.1.1-py3-none-any.whl (338 kB) Downloading pyogrio-0.11.1-cp313-cp313-macosx_12_0_arm64.whl (19.4 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 19.4/19.4 MB 691.6 kB/s eta 0:00:0000:0100:01 Downloading pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl (4.6 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.6/4.6 MB 727.1 kB/s eta 0:00:00a 0:00:01 Installing collected packages: pyproj, pyogrio, geopandas ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3/3 [geopandas]/3 [geopandas] Successfully installed geopandas-1.1.1 pyogrio-0.11.1 pyproj-3.7.2 Note: you may need to restart the kernel to use updated packages.
pip install shapely
Requirement already satisfied: shapely in /opt/anaconda3/lib/python3.13/site-packages (2.1.2) Requirement already satisfied: numpy>=1.21 in /opt/anaconda3/lib/python3.13/site-packages (from shapely) (2.1.3) Note: you may need to restart the kernel to use updated packages.
pip install folium
Requirement already satisfied: folium in /opt/anaconda3/lib/python3.13/site-packages (0.20.0) Requirement already satisfied: branca>=0.6.0 in /opt/anaconda3/lib/python3.13/site-packages (from folium) (0.8.2) Requirement already satisfied: jinja2>=2.9 in /opt/anaconda3/lib/python3.13/site-packages (from folium) (3.1.6) Requirement already satisfied: numpy in /opt/anaconda3/lib/python3.13/site-packages (from folium) (2.1.3) Requirement already satisfied: requests in /opt/anaconda3/lib/python3.13/site-packages (from folium) (2.32.3) Requirement already satisfied: xyzservices in /opt/anaconda3/lib/python3.13/site-packages (from folium) (2022.9.0) Requirement already satisfied: MarkupSafe>=2.0 in /opt/anaconda3/lib/python3.13/site-packages (from jinja2>=2.9->folium) (3.0.2) Requirement already satisfied: charset-normalizer<4,>=2 in /opt/anaconda3/lib/python3.13/site-packages (from requests->folium) (3.3.2) Requirement already satisfied: idna<4,>=2.5 in /opt/anaconda3/lib/python3.13/site-packages (from requests->folium) (3.7) Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/anaconda3/lib/python3.13/site-packages (from requests->folium) (2.3.0) Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/lib/python3.13/site-packages (from requests->folium) (2025.8.3) Note: you may need to restart the kernel to use updated packages.
import pandas as pd
import folium
import numpy as np
import plotly.express as px
from shapely.geometry import Point, Polygon
from IPython.display import IFrame
Data frame¶
Data frames contain more details about the people affected, like their name, profession, and age. I also included the weather category of the day.
deaths = pd.read_csv("Snow_map_Deaths_Adjusted.csv")
pumps = pd.read_csv("Pumps.csv")
df_weather = deaths.copy()
deaths.head()
| death_count | latitude | longitude | Gender | Age | Date | Name | Profession | weather_category | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 51.513441 | -0.138762 | Male | 13 | 1854-08-27 | Joseph Walker | NaN | Rainy/Cloudy |
| 1 | 1 | 51.512364 | -0.137376 | Female | 25 | 1854-09-22 | Jane Brown | Baker | Cold |
| 2 | 1 | 51.514061 | -0.138083 | Male | 71 | 1854-08-19 | James Brown | Old Labourer | Sunny |
| 3 | 1 | 51.513692 | -0.135905 | Male | 12 | 1854-09-09 | Thomas Clark | NaN | Rainy/Cloudy |
| 4 | 1 | 51.513184 | -0.137537 | Male | 75 | 1854-09-29 | Thomas Jones | Retired | Sunny |
pumps.head()
| pump_name | latitude | longitude | |
|---|---|---|---|
| 0 | Broad St. | 51.513341 | -0.136668 |
| 1 | Crown Chapel | 51.513876 | -0.139586 |
| 2 | Gt Marlborough | 51.514906 | -0.139671 |
| 3 | Dean St. | 51.512354 | -0.131630 |
| 4 | So Soho | 51.512139 | -0.133594 |
df_weather.head()
| death_count | latitude | longitude | Gender | Age | Date | Name | Profession | weather_category | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 51.513441 | -0.138762 | Male | 13 | 1854-08-27 | Joseph Walker | NaN | Rainy/Cloudy |
| 1 | 1 | 51.512364 | -0.137376 | Female | 25 | 1854-09-22 | Jane Brown | Baker | Cold |
| 2 | 1 | 51.514061 | -0.138083 | Male | 71 | 1854-08-19 | James Brown | Old Labourer | Sunny |
| 3 | 1 | 51.513692 | -0.135905 | Male | 12 | 1854-09-09 | Thomas Clark | NaN | Rainy/Cloudy |
| 4 | 1 | 51.513184 | -0.137537 | Male | 75 | 1854-09-29 | Thomas Jones | Retired | Sunny |
def age_group(age):
if age < 20:
return "<20"
elif age < 60:
return "20–59"
else:
return "60+"
deaths["AgeGroup"] = deaths["Age"].apply(age_group)
deaths.tail()
| death_count | latitude | longitude | Gender | Age | Date | Name | Profession | weather_category | AgeGroup | |
|---|---|---|---|---|---|---|---|---|---|---|
| 482 | 1 | 51.512447 | -0.137656 | Female | 7 | 1854-08-24 | Elizabeth Jones | NaN | Cold | <20 |
| 483 | 1 | 51.513046 | -0.137562 | Female | 74 | 1854-09-22 | Emma Smith | Old Labourer | Cold | 60+ |
| 484 | 1 | 51.513116 | -0.138337 | Male | 74 | 1854-09-30 | Henry Brown | Old Labourer | Sunny | 60+ |
| 485 | 1 | 51.513692 | -0.135905 | Male | 78 | 1854-09-27 | James Smith | Retired | Rainy/Cloudy | 60+ |
| 486 | 1 | 51.513418 | -0.137930 | Female | 12 | 1854-09-10 | Charlotte Walker | Child | Sunny | <20 |
Scatter and Area Graphs¶
Using a scatter graph and an area graph, I will show groups of people most vulnerable to the disease. In this case, the profession doesn't show a direct influence, but more so for elderly people and young children.
grouped = deaths.groupby(["Gender", "AgeGroup", "Profession"]).size().reset_index(name="Count")
fig = px.scatter(
grouped,
x="AgeGroup",
y="Profession",
size="Count",
color="Gender",
hover_name="Profession",
size_max=60,
color_discrete_map={"Male": "blue", "Female": "red"},
title= "Deaths by Age Group, Gender, and Profession",
)
fig.for_each_trace(
lambda trace: trace.update(
marker=dict(
opacity=0.4 if trace.name == "Male" else 0.4,
line=dict(width=1, color="DarkSlateGrey"))))
fig.update_layout(
plot_bgcolor="white",
xaxis=dict(showgrid=True, gridcolor="lightgrey"),
yaxis=dict(showgrid=True, gridcolor="lightgrey"),
height=700,
)
fig.show()
from IPython.display import IFrame
url = "https://datawrapper.dwcdn.net/ClGV8/2/"
IFrame(src=url, width="100%", height=500)
Note¶
No profession shows a tendency of being aligned with cholera deaths
Weather¶
I will show the weather each day to see if that influenced the number of deaths. It is known that hot weather means higher dehydration, increased body heat, and discomfort, making it more likely for people to perish.
# Column detection
def find_col(df, keyword):
matches = [c for c in df.columns if keyword in c.lower()]
return matches[0] if matches else None
date_col = find_col(df_weather, "date")
gender_col = find_col(df_weather, "gender")
age_col = find_col(df_weather, "age")
weather_col = find_col(df_weather, "weather")
# Prepare data
df_weather[date_col] = pd.to_datetime(df_weather[date_col], errors="coerce")
df_weather = df_weather.dropna(subset=[date_col, gender_col, age_col])
# Sort females then males
df_weather = pd.concat([
df_weather[df_weather[gender_col].str.lower().str.startswith("f")].sort_values(by=age_col),
df_weather[df_weather[gender_col].str.lower().str.startswith("m")].sort_values(by=age_col)
], ignore_index=True)
# Colors
RED = "\033[91m"
BLUE = "\033[94m"
GRAY = "\033[90m"
RESET = "\033[0m"
# Shape age
def get_shape(age):
if age < 15:
return "●"
elif age < 60:
return "▲"
else:
return "■"
def get_symbol(gender, age):
shape = get_shape(age)
color = RED if gender.lower().startswith("f") else BLUE
return f"{color}{shape}{RESET} "
df_weather["symbol"] = df_weather.apply(lambda r: get_symbol(r[gender_col], r[age_col]), axis=1)
# Weather icons
weather_icons = {"Sunny": "☀️", "Rainy/Cloudy": "☁️", "Cold": "❄️"}
# daily
grouped = df_weather.groupby(date_col)
max_deaths = grouped.size().max()
print("\nGRID OF DEATHS PER DAY WITH WEATHER ICON\n")
print("Legend:")
print(f"{RED}●{RESET} Child Female {RED}▲{RESET} Adult Female {RED}■{RESET} Elderly Female")
print(f"{BLUE}●{RESET} Child Male {BLUE}▲{RESET} Adult Male {BLUE}■{RESET} Elderly Male")
print(f"{GRAY}·{RESET} Empty Slot")
print("☀️ Sunny ☁️ Rainy/Cloudy ❄️ Cold\n")
rows = []
for date, group in grouped:
symbols = list(group["symbol"])
count = len(symbols)
if count < max_deaths:
symbols.extend([f"{GRAY}·{RESET} "] * (max_deaths - count))
row_string = "".join(symbols)
weather_val = df_weather.loc[df_weather[date_col] == date, weather_col].iloc[0]
weather_icon = weather_icons.get(weather_val, "☁️")
rows.append((date.strftime("%d/%m/%y"), weather_icon, row_string, weather_val))
max_label_len = max(len(r[0]) for r in rows)
for label, wicon, row, _ in rows:
print(f"{label:<{max_label_len}} {wicon} {row}")
# Weather summary
death_summary = df_weather.groupby(weather_col).size().reindex(["Sunny", "Rainy/Cloudy", "Cold"], fill_value=0)
unique_weather_days = df_weather[[date_col, weather_col]].drop_duplicates().groupby(weather_col).size().reindex(["Sunny", "Rainy/Cloudy", "Cold"], fill_value=0)
ratio = (death_summary / unique_weather_days).round(2)
print("\nWEATHER DAY COUNT\n")
for weather, days in unique_weather_days.items():
icon = weather_icons.get(weather, "")
print(f"{icon} {weather:<15}: {days:>5} days")
print("\nWEATHER SUMMARY (Total Deaths)\n")
for weather, count in death_summary.items():
icon = weather_icons.get(weather, "")
print(f"{icon} {weather:<15}: {count:>5} deaths")
print("\nWEATHER IMPACT ANALYSIS (Deaths / Day)\n")
for weather in ["Sunny", "Rainy/Cloudy", "Cold"]:
icon = weather_icons.get(weather, "")
print(f"{icon} {weather:<15}: {ratio[weather]:>5} deaths/day over {unique_weather_days[weather]} days")
print("\n")
GRID OF DEATHS PER DAY WITH WEATHER ICON Legend: ● Child Female ▲ Adult Female ■ Elderly Female ● Child Male ▲ Adult Male ■ Elderly Male · Empty Slot ☀️ Sunny ☁️ Rainy/Cloudy ❄️ Cold 19/08/54 ☀️ ● ● ● ● ● ● ▲ ● ● ● ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ ■ ■ ■ · · · · 20/08/54 ☁️ ● ● ▲ ■ ■ ■ ▲ · · · · · · · · · · · · · · · · · · · 21/08/54 ☁️ ● · · · · · · · · · · · · · · · · · · · · · · · · · 22/08/54 ☀️ ● ▲ ▲ ▲ ● ▲ ▲ ▲ ■ ■ ■ ■ · · · · · · · · · · · · · · 23/08/54 ❄️ ■ ■ ■ ● ● ● ● ● ▲ ▲ ▲ · · · · · · · · · · · · · · · 24/08/54 ❄️ ● ● ● ● ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ▲ ■ ■ ■ ■ ■ 25/08/54 ❄️ ● ● ▲ ■ ■ ■ ■ ● ▲ ▲ ■ ■ ■ ■ ■ · · · · · · · · · · · 26/08/54 ☁️ ● ● ● ● ● ■ ■ ■ ■ ■ ■ · · · · · · · · · · · · · · · 27/08/54 ☁️ ● ▲ ■ ■ ● ● ● ▲ ▲ ■ ■ ■ ■ · · · · · · · · · · · · · 28/08/54 ☀️ ● ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ · · · · · · · · · · · · 29/08/54 ❄️ ● ● ▲ ● ▲ · · · · · · · · · · · · · · · · · · · · · 30/08/54 ☁️ ▲ ■ ■ ■ ■ ● ● ▲ ▲ · · · · · · · · · · · · · · · · · 31/08/54 ☁️ ● ▲ ▲ ▲ ■ ■ ● ● ■ ■ ■ ■ ■ · · · · · · · · · · · · · 01/09/54 ☁️ ■ ■ ■ ■ ■ ● ▲ ■ ■ ■ ■ ■ · · · · · · · · · · · · · · 02/09/54 ☁️ ● ● ● ▲ ▲ ■ ■ ■ ■ ■ ▲ ▲ ■ · · · · · · · · · · · · · 03/09/54 ❄️ ● ● ● ▲ ▲ ▲ ● ▲ ▲ · · · · · · · · · · · · · · · · · 04/09/54 ☁️ ● ▲ ▲ ▲ ■ ■ ■ ■ ● ▲ ▲ ■ ■ ■ ■ ■ · · · · · · · · · · 05/09/54 ☁️ ▲ ■ ■ ■ ▲ ■ ■ ■ ■ · · · · · · · · · · · · · · · · · 06/09/54 ☁️ ■ ● ▲ ▲ ■ ■ · · · · · · · · · · · · · · · · · · · · 07/09/54 ☀️ ● ▲ ● ● ● ● ▲ ▲ ▲ ▲ ▲ ■ ■ · · · · · · · · · · · · · 08/09/54 ☁️ ● ● ● ▲ ▲ · · · · · · · · · · · · · · · · · · · · · 09/09/54 ☁️ ● ● ● ▲ ▲ ▲ · · · · · · · · · · · · · · · · · · · · 10/09/54 ☀️ ● ● ● ● ● ● ● ● ▲ ■ ■ ■ ■ · · · · · · · · · · · · · 11/09/54 ❄️ ● ▲ ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ▲ ▲ ▲ ■ ■ ■ · · · · 12/09/54 ☁️ ● ▲ ▲ · · · · · · · · · · · · · · · · · · · · · · · 13/09/54 ❄️ ● ● ● ● ▲ ● ● ▲ · · · · · · · · · · · · · · · · · · 14/09/54 ☁️ ● ● ● ▲ ▲ ▲ ▲ ▲ ● ● ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ · 15/09/54 ☁️ ● ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ · · · · · · · · · · 16/09/54 ❄️ ● ● ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ ■ ▲ · · · · · · · · · · · · · 17/09/54 ☁️ ● ▲ ▲ ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ · · · · · · · · · · · · · · 18/09/54 ☀️ ● ● ● ● ▲ ■ ■ ■ ■ ▲ ▲ ▲ · · · · · · · · · · · · · · 19/09/54 ☁️ ▲ · · · · · · · · · · · · · · · · · · · · · · · · · 20/09/54 ☀️ ● ▲ ● ▲ ▲ · · · · · · · · · · · · · · · · · · · · · 21/09/54 ☁️ ▲ ■ ■ ● ● ● ● ▲ ▲ ▲ · · · · · · · · · · · · · · · · 22/09/54 ❄️ ▲ ■ ■ ■ · · · · · · · · · · · · · · · · · · · · · · 23/09/54 ☁️ ● ▲ ▲ ● ● ● ● ● ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ · · · · · · · · 24/09/54 ☁️ ● ● ● ▲ ▲ ▲ ▲ ▲ ■ ■ ■ · · · · · · · · · · · · · · · 25/09/54 ☁️ ● ● ▲ ▲ ▲ ■ ■ ■ ● ● · · · · · · · · · · · · · · · · 26/09/54 ❄️ ● ● ■ ■ ■ ■ ■ · · · · · · · · · · · · · · · · · · · 27/09/54 ☁️ ■ ■ ● ▲ ▲ ▲ ▲ ▲ ▲ ■ ■ ■ ■ ■ ■ · · · · · · · · · · · 28/09/54 ❄️ ▲ ■ ■ ■ ■ ■ ■ ■ ■ ▲ ■ ■ · · · · · · · · · · · · · · 29/09/54 ☀️ ● ▲ ▲ ■ ■ ■ ■ ■ ■ ■ ■ ■ · · · · · · · · · · · · · · 30/09/54 ☀️ ● ▲ ▲ ▲ ● ■ ■ ■ ■ ■ · · · · · · · · · · · · · · · · WEATHER DAY COUNT ☀️ Sunny : 9 days ☁️ Rainy/Cloudy : 23 days ❄️ Cold : 11 days WEATHER SUMMARY (Total Deaths) ☀️ Sunny : 113 deaths ☁️ Rainy/Cloudy : 242 deaths ❄️ Cold : 132 deaths WEATHER IMPACT ANALYSIS (Deaths / Day) ☀️ Sunny : 12.56 deaths/day over 9 days ☁️ Rainy/Cloudy : 10.52 deaths/day over 23 days ❄️ Cold : 12.0 deaths/day over 11 days
Mapping the disease¶
After determining who is more likely to perish from the disease, I will locate each individual using their characteristics to redesign the map and confirm that gender and age are not the primary causes of contracting the disease.
I will also add each character's name to make sure that the user does not forget that these are not numbers, but people with real lives.
deaths["Date"] = pd.to_datetime(deaths["Date"], errors="coerce")
#Color and shape setup
gender_color = {"Male": "blue", "Female": "pink"}
shape_map = {"<20": "circle", "20–59": "triangle", "60+": "square"}
# Create base map
m = folium.Map(location=[51.5133, -0.1368], zoom_start=17, tiles="CartoDB Positron")
# Add pumps
for _, row in pumps.iterrows():
folium.Marker(
location=[row["latitude"], row["longitude"]],
popup=f"<b>Pump:</b> {row['pump_name']}",
tooltip=row["pump_name"],
icon=folium.Icon(color="blue", icon="flag", prefix="fa", icon_color="white")
).add_to(m)
# Add deaths
for _, row in deaths.dropna(subset=["latitude", "longitude"]).iterrows():
color = gender_color.get(row["Gender"], "gray")
shape = shape_map.get(row["AgeGroup"], "circle")
opacity = 0.6 if row["Gender"] == "Male" else 0.4
tooltip_text = (
f"<b>Name:</b> {row['Name']}<br>"
f"<b>Gender:</b> {row['Gender']}<br>"
f"<b>Age:</b> {row['Age']}<br>"
f"<b>Profession:</b> {row['Profession']}<br>"
f"<b>Date of Death:</b> {row['Date'].strftime('%Y-%m-%d') if pd.notnull(row['Date']) else 'Unknown'}"
)
if shape == "circle":
folium.CircleMarker(
location=[row["latitude"], row["longitude"]],
radius=5,
color=color,
fill=True,
fill_color=color,
fill_opacity=opacity,
tooltip=tooltip_text
).add_to(m)
elif shape == "triangle":
folium.RegularPolygonMarker(
location=[row["latitude"], row["longitude"]],
number_of_sides=3,
radius=6,
color=color,
fill=True,
fill_color=color,
fill_opacity=opacity,
tooltip=tooltip_text
).add_to(m)
else:
folium.RegularPolygonMarker(
location=[row["latitude"], row["longitude"]],
number_of_sides=4,
radius=6,
color=color,
fill=True,
fill_color=color,
fill_opacity=opacity,
tooltip=tooltip_text
).add_to(m)
# Legend
legend_html = """
<div style="
position: fixed;
bottom: 30px; right: 20px;
width: 220px; background-color: white;
border: 2px solid grey; z-index:9999; padding: 10px;
">
<b>Legend</b><br>
Gender:<br>
<span style='color:blue;font-size:18px;'>●</span> Male<br>
<span style='color:pink;font-size:18px;'>●</span> Female<br><br>
Age Groups:<br>
◯ <20 years<br>
▲ 20–59 years<br>
■ 60+ years<br><br>
<span style='color:blue;font-size:18px;'>⚑</span> Water Pump
</div>
"""
m
Conclusion¶
This map shows us something similar to what Dr. Snow created, but it gives people a presence and voice inside the tragedy. It is important to maintain connection in what we show. Data by itself creates an impact, but empathy is what influences the spectator. Most of the time, we solemnly focus on getting the point across, but it depends on us to be understood.
Bonus Map¶
from IPython.display import HTML
HTML("""
<div class="flourish-embed flourish-map" data-src="visualisation/25855341">
<script src="https://public.flourish.studio/resources/embed.js"></script>
<noscript><img src="https://public.flourish.studio/visualisation/25855341/thumbnail" width="100%" alt="map visualization" /></noscript>
</div>
""")